/**
@C++ Standard: 11
@Copyright:Copyright (c) 2019 Harold Hu. All rights reserved.
@Author: Created by Harold Hu with CLion on 2019/1/14 10:18.
@File Name: Matrix.h
@Description a simple matrix template realization
*/
#ifndef HAROLD_MATRIX_H
#define HAROLD_MATRIX_H

#include <cstddef>
#include <cstdio>
#include <new>
#include <ostream>

/**
 * @Description a simple matrix template realization
 * @tparam T: element type of matrix
 * @Copyable Yes
 * @Moveable Yes
 * @Attention no default constructor
 */
template<typename T>
class Matrix
{
	using elementType = T;
public:
	
	/**
	 * @Attention Just do remember no default constructor exists
	 */
	Matrix() = delete;
	
	/**
	 * @Description Matrix constructor use placement new T
	 * @tparam Arg: T's constructor parameter's type list
	 * @param rows: row of Matrix
	 * @param cols: column of matrix
	 * @param argCreatT: T's constructor parameter list
	 */
	template<typename ...Arg>
	Matrix(size_t rows, size_t cols, Arg&& ...argCreatT);
	
	/**
	 * @Description Matrix copy constructor
	 * @param rhs: source to copy
	 */
	Matrix(const Matrix& rhs);
	
	/**
	 * @Description Matrix move constructor
	 * @param rhs: source to move
	 */
	Matrix(Matrix&& rhs);
	
	/**
	 * @Description Matrix copy assignment constructor
	 * @param rhs: source to copy
	 */
	Matrix& operator=(const Matrix& rhs);
	
	/**
	 * @Description Matrix move assignment constructor
	 * @param rhs: source to move
	 */
	Matrix& operator=(Matrix&& rhs);
	
	/**
	 * @Description get address of NO.[row][col] element
	 * @param row: row index of the element in matrix
	 * @param col: column index of the element in matrix
	 * @return elementType*: address of NO.[row][col] element
	 */
	inline elementType* getPtr(size_t row, size_t col)
	{
		return _data + row * _cols + col;
	}
	
	/**
	 * @Description get address of first element
	 * @return elementType*: address of first element
	 */
	inline elementType* getData() const
	{
		return _data;
	}
	
	/**
	 * @Description get row of matrix
	 * @return size_t: row of matrix
	 */
	inline size_t getRows() const
	{
		return _rows;
	}
	
	/**
	 * @Description get column of matrix
	 * @return size_t: column of matrix
	 */
	inline size_t getCols() const
	{
		return _cols;
	}
	
	/**
	 * @Description print matrix basic information
	 * @param os: ostream reference
	 * @param matrix: matrix to print
	 * @return std::ostream&: std::ostream reference
	 */
	friend std::ostream& operator<<(std::ostream& os, const Matrix& matrix)
	{
		os << "_rows: " << matrix._rows << " _cols: " << matrix._cols << " _data address: " << matrix._data;
		return os;
	}
	
	/**
	 * @Description destructor to destroy element and release elements' memory
	 */
	virtual ~Matrix()
	{
		for (size_t index = 0; index < _rows * _cols; ++index)
		{
			(_data + index)->~elementType();
		}
		delete[] reinterpret_cast<char*>(_data);
	}

protected:
	size_t _rows;///<	row of matrix
	size_t _cols;///<	column of matrix
	T* _data;///<	data Array address
};


template<typename T>
Matrix<T>::Matrix(const Matrix<T>& rhs):_rows(rhs._rows), _cols(rhs._cols)
{
	if (this != &rhs)
	{
		_data = reinterpret_cast<T*>(new(std::nothrow) char[sizeof(T) * (rhs._rows * rhs._cols)]);
		if (_data)
		{
			_data = reinterpret_cast<T*>(_data);
			for (size_t index = 0; index < rhs._rows * rhs._cols; ++index)
			{
				::new(_data + index) T(*(rhs._data + index));
			}
		}
		else
		{
			printf("%s", "alloc memory for Matrix member failed");
		}
		_rows = rhs._rows;
		_cols = rhs._cols;
	}
}

template<typename T>
Matrix<T>& Matrix<T>::operator=(const Matrix<T>& rhs)
{
	if (this != &rhs)
	{
		for (size_t index = 0; index < _rows * _cols; ++index)
		{
			(_data + index)->~elementType();
		}
		delete[] reinterpret_cast<char*>(_data);
		_data = nullptr;
		_data = reinterpret_cast<T*>(new(std::nothrow) char[sizeof(T) * (rhs._rows * rhs._cols)]);
		if (_data)
		{
			_data = reinterpret_cast<T*>(_data);
			for (size_t index = 0; index < rhs._rows * rhs._cols; ++index)
			{
				::new(_data + index) T(*(rhs._data + index));
			}
		}
		else
		{
			printf("%s", "alloc memory for Matrix member failed");
		}
		_rows = rhs._rows;
		_cols = rhs._cols;
	}
	return *this;
}

template<typename T>
Matrix<T>::Matrix(Matrix&& rhs)
{
	if (this != &rhs)
	{
		_data = rhs._data;
		_rows = rhs._rows;
		_cols = rhs._cols;
		rhs._data = nullptr;
	}
}


template<typename T>
Matrix<T>& Matrix<T>::operator=(Matrix&& rhs)
{
	if (this != &rhs)
	{
		_data = rhs._data;
		_rows = rhs._rows;
		_cols = rhs._cols;
		rhs._data = nullptr;
	}
	return *this;
}

template<typename T>
template<typename... Arg>
Matrix<T>::Matrix(size_t rows, size_t cols, Arg&& ... argCreatT) : _rows(rows), _cols(cols), _data(nullptr)
{
	if (rows * cols > 0)
	{
		_data = reinterpret_cast<T*>(new(std::nothrow) char[sizeof(T) * (rows * cols)]);
		if (_data)
		{
			for (size_t index = 0; index < rows * cols; ++index)
			{
				::new(_data + index) T(std::forward<Arg>(argCreatT)...);
			}
		}
		else
		{
			printf("%s\n", "alloc memory for Matrix member failed");
		}
	}
	else
	{
		printf("%s\n", "rows * cols must > 0");
	}
}


#endif //HAROLD_MATRIX_H
